home *** CD-ROM | disk | FTP | other *** search
- /*
- File: HIDReader.c
-
- Contains: HID Library Example
-
- Version: 1.1 for use with USB DDK 1.4.1
-
-
- Copyright: © 1999-2000 by Apple Computer, Inc., all rights reserved.
-
-
- To get current values and set them, this code requires USB 1.4, which
- is newer than Mac OS 9. The example is written with enough checking
- built in that it can safely execute on earlier USB versions.
-
- This example addresses a number of issues involved with using the HID
- Library to interact with Human Input USB Devices. I have tried to group
- the code for each issue in a single module separated by comments. For
- this reason, not all sections may be necessary for your use. Even within
- a section there may be more code than you need to use. Error checking
- and memory allocation is rather simplistic to easily survive the affects
- of removing unnecessary code.
-
- This example code is normally built within HIDReader.mcp, a Metrowerks
- 5.3 Pro project. Universal Interfaces 3.3 have been downloaded from Apple's
- Developer Support and installed within Code Warrior's MacOS Support folder
- in order to have the correct version of HID.h.
- */
-
- #define CALL_NOT_IN_CARBON 1
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <errors.h>
- #include <USB.h>
- #include <HID.h>
-
- // How do we find out what vendor ID and product ID we are looking for?
- // Use USB Prober to check under the Device Descriptor. This version
- // is looking for a USB device that most developers will have available,
- // the Apple USB mouse.
-
- /*
- Composite device (M4848)
- Device VendorID/ProductID:. 0x05ac/0x0301 (Apple Computer, Inc.)
-
- HID Descriptor:
- 05 01 Usage Page (Generic Desktop)
- 09 02 Usage (Mouse)
- A1 01 Collection (Application)
- 05 09 Usage Page (Button)
- 19 01 Usage Minimum...... (1)
- 29 01 Usage Maximum...... (1)
- 15 00 Logical Minimum.... (0)
- 25 01 Logical Maximum.... (1)
- 95 01 Report Count....... (1)
- 75 01 Report Size........ (1)
- 81 02 Input (Data)
- 95 01 Report Count....... (1)
- 75 07 Report Size........ (7)
- 81 03 Input (Constant)
- 05 01 Usage Page (Generic Desktop)
- 09 01 Usage (Pointer)
- A1 00 Collection (Physical)
- 09 30 Usage (X)
- 09 31 Usage (Y)
- 15 81 Logical Minimum.... (-127)
- 25 7F Logical Maximum.... (127)
- 75 08 Report Size........ (8)
- 95 02 Report Count....... (2)
- 81 06 Input (Data)
- C0 End Collection
- C0 End Collection
- */
- #define myVendorID 0x05AC // Apple
- #define myProductID 0x0301 // USB mouse
-
- // I have also used USB Prober to examine the Parsed Report Descriptor
- // under the device's Configuration Descriptor. I chose a usage that i
- // knew allowed values to be set. In this case RemainingCapacityLimit,
- // which is usage 41 on HID usage page 133. Find the listing for this
- // item in the descriptor and scan upward to find report ID and report
- // size that correspond to it. Also note that the item is described as
- // Feature(Data, Variable, Absolute, Non-volatile). In the same fashion
- // it may be possible for you to extract the necessary information to
- // interact with the HID item of your choice.
-
- #define targetUsage 0x30 // X
- #define targetUsagePage 0x01 // Generic Desktop
- #define targetCollection 0x01 // Pointer
- #define targetCollectionPage 0x01 // Generic Desktop
- #define targetReportID 0 // can be found from HID Library
- #define targetReportBitSize 24 // cannot be found from HID Library.
-
-
- // Forward declarations:
- void InstallReportHandler();
- void RemoveReportHandler();
- void MyHIDReportHandler(void * inHIDReport, UInt32 inHIDReportLength,
- UInt32 inRefcon);
-
- // Data shared between functions
- HIDDeviceDispatchTablePtr mHIDDispatchTable;
- USBDeviceDescriptorPtr mUSBDeviceDesc;
- HIDPreparsedDataRef mParsedHIDRef;
- HIDDeviceConnectionRef mHIDDeviceConnectionRef;
-
- UInt32 mReportID = targetReportID;
- UInt32 mCollection = 0;
- UInt32 mReportSize = (targetReportBitSize + 7)/8; // Room for odd bits.
-
-
- int main(void)
- {
- // Data shared between sections
- CFragConnectionID usbConnID;
- CFragSymbolClass symClass;
- THz currentZone;
- OSErr err;
-
-
- // ***** Step 1: Find Your Device:
-
- USBDeviceRef usbDeviceRef = kNoDeviceRef;
- Boolean foundMyDevice = false;
-
- while (!foundMyDevice)
- {
- err = USBGetNextDeviceByClass (&usbDeviceRef, &usbConnID, kUSBHIDClass,
- kUSBAnySubClass, kUSBAnyProtocol);
- if (err) return err;
-
- // Need to be in the system zone when we search for the symbol.
- currentZone = GetZone ();
- SetZone (SystemZone ());
- err = FindSymbol (usbConnID, "\pTheUSBDriverDescription",
- (Ptr *)&mUSBDeviceDesc, &symClass);
- SetZone (currentZone);
-
- if (mUSBDeviceDesc->vendor == myVendorID &&
- mUSBDeviceDesc->product == myProductID)
- {
- foundMyDevice = true;
- }
-
- // If the driver that matched our device was the HID class driver,
- // it does not belong to a specific device, so it has vendor and
- // product ids of 0, 0. In that case, we have to check farther.
-
- if (mUSBDeviceDesc->vendor == 0 &&
- mUSBDeviceDesc->product == 0)
- {
- // Now we are going to get information from the device itself.
- currentZone = GetZone();
- SetZone(SystemZone());
- err = FindSymbol(usbConnID, "\pTheHIDDeviceDispatchTable",
- (Ptr *)&mHIDDispatchTable, &symClass);
- SetZone(currentZone);
- if (err) continue; // There may be other devices to check.
-
- UInt16 theVendorID = 0;
- UInt32 size = sizeof(UInt16);
- err = (*mHIDDispatchTable->pHIDGetDeviceInfo)(kHIDGetInfo_VendorID,
- &theVendorID, &size);
- if (err) continue;
-
- UInt16 theProductID = 0;
- size = sizeof(UInt16);
- err = (*mHIDDispatchTable->pHIDGetDeviceInfo)(kHIDGetInfo_ProductID,
- &theProductID, &size);
- if (err) continue;
-
- if (theVendorID == myVendorID && theProductID == myProductID)
- {
- foundMyDevice = true;
- }
- }
- }
-
-
- // ***** Step 2: HID Library Setup:
-
- UInt8 * mHIDReportDesc = nil;
- UInt32 mHIDReportDescLength = 0;
-
- currentZone = GetZone();
- SetZone(SystemZone());
- err = FindSymbol(usbConnID, "\pTheHIDDeviceDispatchTable",
- (Ptr *)&mHIDDispatchTable, &symClass);
- SetZone(currentZone);
- if (err) return err;
-
- // Find out what size buffer we need for HID report descriptor.
- err = (*mHIDDispatchTable->pHIDGetHIDDescriptor)(kUSBReportDesc, 0,
- nil, &mHIDReportDescLength);
- if (err) return err;
-
- mHIDReportDesc = (UInt8 *)NewPtrClear(mHIDReportDescLength);
- if (mHIDReportDesc == nil) return MemError();
-
- err = (*mHIDDispatchTable->pHIDGetHIDDescriptor)(kUSBReportDesc, 0,
- mHIDReportDesc, &mHIDReportDescLength);
- if (!err)
- err = HIDOpenReportDescriptor(mHIDReportDesc, mHIDReportDescLength,
- &mParsedHIDRef, kHIDFlag_StrictErrorChecking);
- // Some HID report descriptors may have minor errors that trip up our call
- // when kHIDFlag_StrictErrorChecking is on. If may be possible to try this
- // with 0 error checking and have the open succeed.
-
- // No longer need the raw descriptor.
- DisposePtr((char *)mHIDReportDesc);
-
- if (err) goto finalcleanup;
-
-
- // ***** Step 3: Finding Report Info:
-
- // In order to get and set values, you need to know the report ID of the
- // report that will pass those values back and forth to your device. This
- // information is usually known from the HID report descriptor and you
- // should be able to declare it as a constant.
-
- // In those cases where you may want to find a particular type of usage in
- // a number of different devices (each with differing HID reports), or just
- // don't know the report ID, you can still find the report ID dynamically
- // using the HID Library. You do need to know the usage, usage page, and
- // report type (input, output, or feature) of the value you desire. This
- // example is looking for a usage value, so it will use HIDValueCaps, but
- // if you want button settings, you would use the corresponding HIDButtonCaps.
-
- // The code below is going to loop through the tables looking for the correct
- // usage and usage page. What happens if you have several fields that use the
- // same usage and usage page? To clearly specify an item, the HID Library
- // passes a collection id with most of it's calls. Usually it is sufficient
- // to pass a value of 0 which will match any collection. If a collection value
- // is necessary to clearly specify which usage you need, the collection numbers
- // are assigned by HID Library when it does it's pre-parsing, so you will have
- // to first find the correct collection.
-
- HIDCaps mMaxCaps;
- UInt32 numCollections;
- UInt32 numInputValues;
- HIDCollectionNode * cnPtr = nil;
- HIDValueCaps * vcPtr = nil; // // For buttons, use HIDButtonCaps.
-
-
- // Get statistics on how many of each type of HID item.
- err = HIDGetCaps(mParsedHIDRef, &mMaxCaps);
-
- if (!err)
- {
- // Find the collection first
- numCollections = mMaxCaps.numberCollectionNodes;
- cnPtr =
- (HIDCollectionNode *)NewPtrClear(sizeof(HIDCollectionNode) * numCollections);
-
- if (cnPtr != nil)
- {
- err = HIDGetCollectionNodes(cnPtr, &numCollections, mParsedHIDRef);
-
- if (!err)
- { // We have our information and can search for specific case.
- for (int i = 0; i < numCollections; i++)
- {
- // The advantage of climbing through the collection node
- // array is that if the HID report description is complex
- // and uses multiple collecitons that have the same usage,
- // we can find the collection that contains the target
- // collection or the collection that is the sibling of the
- // target collection. Then use that in the compare of a
- // further iteration, until we get to the unique collection
- // number we need.
- if (cnPtr[i].collectionUsage == targetCollection &&
- cnPtr[i].collectionUsagePage == targetCollectionPage)
- {
- mCollection = i;
- break;
- }
- }
- }
-
- DisposePtr((char *)cnPtr);
- }
- // If we had an error looking for the collection, we could bail from here
- // or just press on and hope that the first usage we find below works out.
- if (err) goto finalcleanup;
-
- // There can be 6 arrays of HID report info: Both values and buttons can
- // be grouped into kHIDInputReport, kHIDOutputReport, and kHIDFeatureReport.
- // For simplicity, we will only check for an input value at this time.
- numInputValues = mMaxCaps.numberInputValueCaps;
- vcPtr =
- (HIDValueCaps *)NewPtrClear(sizeof(HIDValueCaps) * numInputValues);
-
- if (vcPtr != nil)
- {
- err = HIDGetValueCaps(kHIDInputReport, vcPtr, &numInputValues,
- mParsedHIDRef);
- if (!err)
- { // We have our information and can search for specific case.
- for (int i = 0; i < numInputValues; i++)
- {
- // Before treating the usage field as a real usage, we
- // should have checked the isRange flag. However, we
- // won't match the targetUsage even if it is usageMin.
- if (vcPtr[i].u.notRange.usage == targetUsage &&
- vcPtr[i].usagePage == targetUsagePage &&
- // If the colleciton usage and usage page was
- // unique, we skip the previous looping through
- // the collection nodes and just compare against
- // vcPtr[i].collectionUsage and vcPtr[i].collectionUsagePage
- // here.
- vcPtr[i].collection == mCollection)
- {
- // Notice if we didn't have to find a unique collection
- // first (and skipped it in the compare above), we could
- // still find the collection here to use in later function
- // calls.
- // mCollection = vcPtr[i].collection;
- mReportID = vcPtr[i].reportID;
- break;
- }
- }
- }
-
- DisposePtr((char *)vcPtr);
- }
- }
-
- if (err) goto finalcleanup;
-
-
- // ***** Step 4: Setup Report Handler:
-
- // The report handler will normally just let us know when a value has changed.
- // But what if we want to Get an initial value?
- // Normally we would do some sychronous read of the value we are interested in.
- // However, USB is running at interrupt time and HID Library has gone to great
- // lengths to shield us from that. So the way to cleanly get a value is to use
- // the report handler. We will install the report handler and then request a
- // report for the value we are interested in.
-
- InstallReportHandler();
-
-
- // ***** Step 5: Get Current Value:
-
- // Installing the report handler will let us know when values change
- // because that is when reports are issued. To get initial values, however,
- // we may have to request them.
-
- // Prior to USB 1.4, the pHIDGetReport vector was nil, so there was no
- // way to request a current value until after Mac OS 9.0.
- if (mHIDDispatchTable->pHIDGetReport == nil) goto waitforinput;
-
- // We are not only asking for the value we are interested in, but also
- // any value that shares that reportID.
- err = (*mHIDDispatchTable->pHIDGetReport)(mHIDDeviceConnectionRef,
- kHIDInputReport, mReportID, MyHIDReportHandler, 0);
-
-
- // ***** Step 6: Change Value:
-
- //****************************************************************************
- // WARNING! We want an example of setting a value on a HID device. Well,
- // the only device we could count on being available for this sample code
- // was the Apple mouse. It has only input reports, because it is only sending
- // data to the Macintosh and doesn't expect any back. I will go ahead with
- // this example and construct a report using the kHIDInputReport type so you
- // can see how it is done. But clearly for setting a value, we should only be
- // sending values to kHIDOutputReports or those kHIDFeatureReports that are
- // used to configure the device. For now, when we actually send this report
- // off to the device, it will reply with error -6912, stalling the pipe because
- // it doesn't understand. So we get to see a cascade of error messages output
- // to the USB Prober Expert Log. We'll recover and you can see the other input
- // reports get handled just fine.
- //****************************************************************************
-
- UInt32 newValue = 30; // Random number for our example.
-
- // To use SetValue, we discovered that we needed more information than
- // was easily available. To recitify this, future releases of the HID
- // Library may include an expanded API that will handle some of the
- // setup we are doing here.
-
- // Prior to USB 1.4, the pHIDSetReport vector was nil, so there was no
- // way to set a value until after Mac OS 9.0.
- if (mHIDDispatchTable->pHIDSetReport == nil) goto waitforinput;
-
- // Create storage for the HID report we are going to send.
- // The first thing we need to know is what size record to create. There
- // is NO API in the HID Library that allows us to find this out. So for
- // now, we must look at the HID Report Descriptor displayed by USB Prober.
- // We will have to add up all the field sizes that share that report ID.
-
- // The size displayed is in bits, but the size we use in HID Library calls
- // is in byte. In this example we have a total size of 24. To get bytes we
- // use the formula: bytes = (bits + 7) / 8, which keeps us from rounding off
- // those odd numbers of bits. Not only that, but when there are many possible
- // reportID's, we need an extra byte to hold the reportID we want. Since the
- // mouse has only a single report it issues, there is no need for that extra
- // byte in this example.
-
- UInt8 * reportPtr = (UInt8 *)::NewPtrClear(mReportSize);
- if (reportPtr == nil) goto waitforinput;
-
- // In the cases where our report must contain the report ID, HIDSetUsageValue
- // doesn't set it for us. We must do so manually. (In this example, since the
- // report ID is 0, it doesn't matter that we put it in the first byte where we
- // don't need it.)
- *reportPtr = mReportID;
-
- // The HID Library can now build the necessary HID report. Note here that
- // a collection value of 0 is usually sufficient for the setup calls.
- err = HIDSetUsageValue(kHIDInputReport, targetUsagePage, mCollection,
- targetUsage, newValue, mParsedHIDRef, reportPtr, mReportSize);
- if (err) goto setvaluecleanup;
-
- // Actually send the report.
- err = (*mHIDDispatchTable->pHIDSetReport)(mHIDDeviceConnectionRef,
- kHIDInputReport, mReportID, reportPtr, mReportSize);
- if (err) goto setvaluecleanup;
-
- // Optional step. When a value is sent to a HID device, it usually does not
- // reply with a new report showing the changed value. So if you are using
- // your report handler to keep track of current values, you may want to
- // request a new report to verify that our set report actually worked.
- err = (*mHIDDispatchTable->pHIDGetReport)(mHIDDeviceConnectionRef,
- kHIDInputReport, mReportID, MyHIDReportHandler, 0);
-
- setvaluecleanup:
- // HID Library makes it's own copy of our data, so we can free it now.
- if (reportPtr != nil) DisposePtr((char *)reportPtr);
-
-
- // ***** Step 7: Display Output:
-
- // Allow time for reports to come in and be handled before quiting.
- // The report handler will be given time to add output to USB Prober's
- // Expert Log window.
- waitforinput:
- printf("View report handling in USB Prober's Expert Log window.\n");
- printf("Type 'q' to quit handling reports.\n");
- fflush(nil);
- int stopChar = 0;
- while (stopChar != 'q' && stopChar != 'Q') stopChar = getchar();
-
-
- // ***** Step 8: Cleanup:
-
- finalcleanup:
- RemoveReportHandler();
-
- printf("Removed report handler.\n");
- fflush(nil);
-
- if (mParsedHIDRef != nil)
- {
- HIDCloseReportDescriptor(mParsedHIDRef);
- mParsedHIDRef = nil;
- }
-
- return 0;
- }
-
-
- // • ReportHandler
- //
- // The key to how the report handler works is that when it gets called to
- // process a report, it must pass the report through one of the HID Library
- // functions to extract the desired value. These decoder functions are called
- // HIDGetxxx (not HIDGetxxxCaps): HIDGetUsageValue, HIDGetScaledUsageValue,
- // HIDGetUsageValueArray, HIDGetButtons, and HIDGetButtonsOnPage.
- // Reports are compressed data that may contain multiple values within. They
- // also may or may not have reportID as the first byte of data. A brute force
- // way to handle the confusion is to just set up a series of decode calls for
- // each value you are interested in and apply the incomming report to each of
- // them, accepting only those that return with noErr.
-
- void MyHIDReportHandler(void * inHIDReport, UInt32 inHIDReportLength,
- UInt32 inRefcon)
- {
- // In the HID Browser, i have multiple devices open, each with it's own
- // set of values. There i pass the pointer to the device's variables
- // as the refcon.
- #pragma unused(inRefcon)
-
- // A special note for using HIDGetButtonsOnPage: This call is going to
- // return an array of HIDUsage's that corresponds to each button that is
- // on. In the advent of there being no buttons on, rather than just
- // returning a 0 length array, it comes back with kHIDUsageNotFoundErr.
-
- // We're making it easy. We're only looking for one specific value.
- SInt32 reportValue;
- OSErr err;
-
- err = HIDGetUsageValue(kHIDInputReport, targetUsagePage, mCollection,
- targetUsage, &reportValue, mParsedHIDRef, inHIDReport,
- inHIDReportLength);
- if (err) return;
-
- // My first attempt at a cheap thing to do with our output, was to use printf.
- // After crashing due to interference between printf's in the main code thread
- // and this handler, i hit upon the much better expediant of sending our output
- // to USB Prober's Expert Log window. Plus, this is a great example of an
- // invaluable debugging tool for USB.
- USBExpertStatusLevel(kUSBStatusLevelGeneral, 0,
- "\pMyHIDReportHandler Value: ", (UInt32)reportValue);
-
- // We're done handling the report, but now that this example gets reports from
- // the mouse, they are no longer going on to the system to move the cursor. The
- // polite thing to do is to forward the reports on to whoever else may need them.
- if (mHIDDispatchTable->pHIDCallPreviousReportHandler != nil &&
- mHIDDeviceConnectionRef != 0)
- {
- // Call previous handler.
- err = (*mHIDDispatchTable->pHIDCallPreviousReportHandler)
- (mHIDDeviceConnectionRef, inHIDReport, inHIDReportLength);
- }
- }
-
-
- // • InstallReportHandler
- //
- // Since InstallReportHandler can be called from various locations,
- // it does all it's own setup validation before doing the actual
- // install. If it fails, it leaves the state such that RemoveReportHandler
- // can also be called and perform only necessary cleanup.
-
- void InstallReportHandler()
- {
- HIDDeviceConnectionRef tempDeviceConnectionRef;
- OSErr err;
-
- // Checking to see if device open already.
- if (mHIDDeviceConnectionRef != 0) return;
-
- // Are we setup to open?
- if (mHIDDispatchTable == nil) return;
-
- // Open the device.
- err = (*mHIDDispatchTable->pHIDOpenDevice)
- (&tempDeviceConnectionRef, kHIDPerm_ReadWriteShared, 0);
-
- if (err) return;
-
- // The device is open, so let's install our handler.
- err = (*mHIDDispatchTable->pHIDInstallReportHandler)
- (tempDeviceConnectionRef, 0, MyHIDReportHandler, 0);
-
- // Don't leave device open if we got an error.
- if (err)
- {
- (*mHIDDispatchTable->pHIDCloseDevice)(tempDeviceConnectionRef);
- return;
- }
-
- // Signal successful opening.
- mHIDDeviceConnectionRef = tempDeviceConnectionRef;
- }
-
-
- // • RemoveReportHandler
- //
- // Since RemoveReportHandler can be called from various locations,
- // it does all it's own setup validation before doing the actual remove.
-
- void RemoveReportHandler()
- {
- OSErr err;
-
- // If no indication that we successfully installed, don't remove.
- if (mHIDDeviceConnectionRef == 0) return;
-
- // Remove the handler.
- err = (*mHIDDispatchTable->pHIDRemoveReportHandler)(mHIDDeviceConnectionRef);
-
- // Removing the report handler also restores the previous handler, if any.
- // In event of an error so that is not done, it may still be better to fall
- // through and try to close the device anyway?
- if (err) return;
-
- // Release the device.
- err = (*mHIDDispatchTable->pHIDCloseDevice)(mHIDDeviceConnectionRef);
-
- if (err) return;
-
- // Signal successful closing.
- mHIDDeviceConnectionRef = 0;
- }
-
-